//
//  JGMusicAnimationViewFFT.m
//  JGMusicAnimation
//
//  Created by Mei Jigao on 16/3/11.
//  Copyright © 2016年 MEETStudio. All rights reserved.
//

#import "JGMusicAnimationViewFFT.h"
#import "JGMusicAnimationConfig.h"
#import <Accelerate/Accelerate.h>
#import <AVFoundation/AVFoundation.h>

@interface JGMusicAnimationViewFFT () {
    
    // buffers
    float sampleRate;
    float *dataBuffer;
    size_t bufferCapacity;
    size_t bufferIndex;
    
    // ftt setup
    FFTSetup fftSetup;
    COMPLEX_SPLIT splitA;
    int log2n, nOver2;
    
    // draw
    float *sinAngle;
    float *heightsByFrequency;
    
    // y衰减
    float *speeds, *times, *tSqrts, *vts, *deltaHeights;
}

@property (nonatomic, strong) CADisplayLink *displaylink;

@end

@implementation JGMusicAnimationViewFFT

+ (Class)layerClass {
    
    return [CAGradientLayer class];
}

#pragma mark - init & dealloc
- (instancetype)init {
    
    self = [super init];
    if (self) {
        
        [self setup];
    }
    
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    
    self = [super initWithCoder:aDecoder];
    if (self) {
        
        [self setup];
    }
    
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame {
    
    self = [super initWithFrame:frame];
    if (self) {
        
        [self setup];
    }
    
    return self;
}

- (void)setup {
    
    [self setBackgroundColor:[UIColor clearColor]];
    
    // ftt setup
    dataBuffer = (float *)malloc(JGMAViewDrawFrames * sizeof(float));
    log2n = log2f(JGMAViewDrawFrames);
    int nlog2 = 1 << log2n;
    assert(nlog2 == JGMAViewDrawFrames);
    
    nOver2 = JGMAViewDrawFrames / 2;
    bufferCapacity = JGMAViewDrawFrames;
    bufferIndex = 0;
    splitA.realp = (float *)malloc(nOver2 * sizeof(float));
    splitA.imagp = (float *)malloc(nOver2 * sizeof(float));
    fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2);
    
    // audio session
    sampleRate = [[AVAudioSession sharedInstance] sampleRate];
    
    // draw timer
    _displaylink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateHeights)];
    [_displaylink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    [_displaylink setPaused:YES];
    
    [self setDefaultProperties];
}

- (void)removeDisplayTimer {
    
    [_displaylink invalidate], _displaylink = nil;
}

- (void)dealloc {
    
    JGMALog(@"");
    [_displaylink invalidate], _displaylink = nil;
    
    [self freeBuffersIfNeeded];
}

#pragma mark - Properties
- (void)setDefaultProperties {
    
    [self setMinFrequency:JGMAViewFFTFrequencyMinDefault];
    [self setMaxFrequency:JGMAViewFFTFrequencyMaxDefault];
    [self setNumOfBins:JGMAViewNumOfBinsDefault];
    [self setGain:JGMAViewGainDefault];
    [self setWithSin:YES];
    [self setSinSwaponly:NO];
    [self setLineWidth:1.f];
    
    [self setDrawInterval:JGMAViewDrawIntervalDefault];
    
    NSArray *colors = [NSArray arrayWithObjects:
                       [UIColor colorWithRed:255 / 255.0 green:86 / 255.0 blue:20 / 255.0 alpha:1],
                       [UIColor colorWithRed:255 / 255.0 green:250 / 255.0 blue:71 / 255.0 alpha:1],
                       [UIColor colorWithRed:249 / 255.0 green:59 / 255.0 blue:4 / 255.0 alpha:1],
                       [UIColor colorWithRed:249 / 255.0 green:59 / 255.0 blue:4 / 255.0 alpha:1],
                       [UIColor colorWithRed:255 / 255.0 green:250 / 255.0 blue:71 / 255.0 alpha:1],
                       [UIColor colorWithRed:249 / 255.0 green:59 / 255.0 blue:4 / 255.0 alpha:1],
                       [UIColor colorWithRed:240 / 255.0 green:24 / 255.0 blue:0 / 255.0 alpha:1],
                       [UIColor colorWithRed:251 / 255.0 green:114 / 255.0 blue:32 / 255.0 alpha:1],
                       [UIColor colorWithRed:249 / 255.0 green:59 / 255.0 blue:4 / 255.0 alpha:1],
                       [UIColor colorWithRed:240 / 255.0 green:24 / 255.0 blue:0 / 255.0 alpha:1],
                       nil];
    [self setColors:colors];
}

- (void)setMinFrequency:(float)minFrequency {
    
    _minFrequency = MAX(minFrequency, JGMAViewFFTFrequencyMin);
}

- (void)setMaxFrequency:(float)maxFrequency {
    
    _maxFrequency = MIN(maxFrequency, JGMAViewFFTFrequencyMax);
}

- (void)setNumOfBins:(NSUInteger)numOfBins {
    
    _numOfBins = MAX(MIN(numOfBins, JGMAViewNumOfBinsMax), JGMAViewNumOfBinsMin);
    
    // reset buffers
    [self freeBuffersIfNeeded];
    
    // create buffers
    heightsByFrequency = (float *)calloc(sizeof(float), _numOfBins);
    speeds = (float *)calloc(sizeof(float), _numOfBins);
    times = (float *)calloc(sizeof(float), _numOfBins);
    tSqrts = (float *)calloc(sizeof(float), _numOfBins);
    vts = (float *)calloc(sizeof(float), _numOfBins);
    deltaHeights = (float *)calloc(sizeof(float), _numOfBins);
    
    sinAngle = malloc(_numOfBins * sizeof(float));
    float angleGap = ((arc4random() % 10000) / 10000.f + 0.5) * 0.1;
    float angle = 0;
    sinAngle[0] = sin(angle);
    for (int i = 1; i < _numOfBins; i++) {
        
        sinAngle[i] = sin(angle);
        if ((sinAngle[i - 1] > 0 && sinAngle[i] < 0) || (sinAngle[i - 1] < 0 && sinAngle[i] > 0)) {
            
            angleGap = ((arc4random() % 10000) / 10000.f + 0.5) * 0.1;
        }
        angle += angleGap;
    }
}

- (void)setGain:(CGFloat)gain {
    
    _gain = MAX(MIN(gain, JGMAViewGainMax), JGMAViewGainMin);
}

- (void)setLineWidth:(CGFloat)lineWidth {
    
    _lineWidth = lineWidth;
}

- (void)setWithSin:(BOOL)withSin {
    
    _withSin = withSin;
}

- (void)setSinSwaponly:(BOOL)sinSwaponly {
    
    _sinSwaponly = sinSwaponly;
}

- (void)setDrawInterval:(CGFloat)drawInterval {
    
    CGFloat interval = MAX(MIN(JGMAViewDrawIntervalMax, drawInterval), JGMAViewDrawIntervalMin);
    
    [_displaylink setFrameInterval:interval];
}

- (void)setColors:(NSArray *)colors {
    
    _colors = colors;
    
    CAGradientLayer *gradientLayer = (id)[self layer];
    
    // 设置颜色线条分布的方向
    gradientLayer.startPoint = CGPointMake(0.0, 0.0);
    gradientLayer.endPoint = CGPointMake(0.0, 1.0);
    
    // 创建颜色数组
    NSMutableArray *tmp = [NSMutableArray array];
    for (NSInteger i = 0; i < [_colors count]; i++) {
        
        UIColor *color = [_colors objectAtIndex:i];
        [tmp addObject:(id)color.CGColor];
    }
    
    [gradientLayer setColors:[NSArray arrayWithArray:tmp]];
}

#pragma mark - Timer Callback
- (void)updateHeights {
    
    // delay from last frame
    float delay = _displaylink.duration * _displaylink.frameInterval;
    
    // increment time
    vDSP_vsadd(times, 1, &delay, times, 1, _numOfBins);
    
    // clamp time
    static const float timeMin = 1.5, timeMax = 10;
    vDSP_vclip(times, 1, &timeMin, &timeMax, times, 1, _numOfBins);
    
    // increment speed
    float g = delay;
    vDSP_vsma(times, 1, &g, speeds, 1, speeds, 1, _numOfBins);
    
    // increment height
    vDSP_vsq(times, 1, tSqrts, 1, _numOfBins);
    vDSP_vmul(speeds, 1, times, 1, vts, 1, _numOfBins);
    float aOver2 = g / 2;
    vDSP_vsma(tSqrts, 1, &aOver2, vts, 1, deltaHeights, 1, _numOfBins);
    vDSP_vneg(deltaHeights, 1, deltaHeights, 1, _numOfBins);
    vDSP_vadd(heightsByFrequency, 1, deltaHeights, 1, heightsByFrequency, 1, _numOfBins);
    
    [self _refreshDisplay];
}

#pragma mark - Update Buffers
- (void)setSampleData:(float *)data length:(int)length {
    // fill the buffer with our sampled data. If we fill our buffer, run the
    // fft.
    int inNumberFrames = length;
    int read = (int)(bufferCapacity - bufferIndex);
    if (read > inNumberFrames) {
        
        memcpy((float *)dataBuffer + bufferIndex, data, inNumberFrames * sizeof(float));
        bufferIndex += inNumberFrames;
    }
    else {
        // if we enter this conditional, our buffer will be filled and we should
        // perform the FFT.
        memcpy((float *)dataBuffer + bufferIndex, data, read * sizeof(float));
        
        // reset the index.
        bufferIndex = 0;
        
        // fft
        vDSP_ctoz((COMPLEX *)dataBuffer, 2, &splitA, 1, nOver2);
        vDSP_fft_zrip(fftSetup, &splitA, 1, log2n, FFT_FORWARD);
        vDSP_ztoc(&splitA, 1, (COMPLEX *)dataBuffer, 2, nOver2);
        
        // convert to dB
        Float32 one = 1, zero = 0, adjust0DB = JGMAViewFFTAdjust0DB;
        vDSP_vsq(dataBuffer, 1, dataBuffer, 1, inNumberFrames);
        vDSP_vsadd(dataBuffer, 1, &adjust0DB, dataBuffer, 1, inNumberFrames);
        vDSP_vdbcon(dataBuffer, 1, &one, dataBuffer, 1, inNumberFrames, 0);
        vDSP_vthr(dataBuffer, 1, &zero, dataBuffer, 1, inNumberFrames);
        
        // aux
        float mul = (sampleRate / bufferCapacity) / 2;
        int minFrequencyIndex = _minFrequency / mul;
        int maxFrequencyIndex = _maxFrequency / mul;
        int numDataPointsPerColumn = (maxFrequencyIndex - minFrequencyIndex) / _numOfBins;
        float maxHeight = 0;
        
        for (NSUInteger i = 0; i < _numOfBins; i++) {
            // calculate new column height
            float avg = 0;
            vDSP_meanv(dataBuffer + minFrequencyIndex + i * numDataPointsPerColumn, 1, &avg, numDataPointsPerColumn);
            
            // gain 高度补偿、高度限制
            CGFloat columnHeight = MIN(avg * _gain, CGRectGetHeight(self.frame) * 0.5);
            maxHeight = MAX(maxHeight, columnHeight);
            
            // set column height, speed and time if needed
            if (columnHeight > heightsByFrequency[i]) {
                heightsByFrequency[i] = columnHeight;
                speeds[i] = 0;
                times[i] = 0;
            }
        }
    }
}

- (void)shouldDrawing:(BOOL)draw {
    
    [_displaylink setPaused:!draw];
}

- (void)updateWithBuffer:(float *)buffer bufferSize:(UInt32)bufferSize {
    
    [self setSampleData:buffer length:bufferSize];
    if ([_displaylink isPaused]) {
        
        [_displaylink setPaused:NO];
    }
}

#pragma mark - Drawing
- (void)drawRect:(CGRect)rect {
    
    if ([self isHidden]) {
        return;
    }
    
    //贝塞儿曲线参数
    CGFloat halfH = CGRectGetHeight(rect) * 0.5;
    CGFloat gap = ceil(CGRectGetWidth(rect) / (_numOfBins - 1));
    
    //贝塞儿曲线路径
    CGFloat x = 0;
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, nil, x, halfH);
    
    float *line = malloc(_numOfBins * sizeof(float));
    for (int i = 0; i < _numOfBins; i++) {
        
        line[i] = heightsByFrequency[i];
        if (_withSin) {
            
            line[i] = heightsByFrequency[i] * sinAngle[i];
            if (_sinSwaponly) {
                
                line[i] = heightsByFrequency[i] * (sinAngle[i] > 0 ? 1 : -1);
            }
        }
    }
    
    for (NSUInteger i = 1; i < _numOfBins - 2; i++) {
        
        CGFloat xt = x, yt = halfH - line[i];
        CGFloat xc = (x + x + gap) * 0.5;
        CGFloat yc = halfH - (line[i] + line[i + 1]) * 0.5;
        
        CGPathAddQuadCurveToPoint(path, nil, xt, yt, xc, yc);
        
        x += gap;
    }
    
    CGFloat xt = x, yt = halfH - line[_numOfBins - 2];
    CGFloat xc = x + gap;
    CGFloat yc = halfH - line[_numOfBins - 1];
    
    CGPathAddQuadCurveToPoint(path, nil, xt, yt, xc, yc);
    
    free(line);
    
    // 贝塞儿曲线的Layer
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    [shapeLayer setPath:path];
    [shapeLayer setStrokeColor:[[UIColor whiteColor] CGColor]];
    [shapeLayer setFillColor:[[UIColor clearColor] CGColor]];
    [shapeLayer setLineWidth:_lineWidth];
    
    // 绘制完整贝塞儿曲线
    shapeLayer.strokeStart = 0;
    shapeLayer.strokeEnd = 1.0;
    self.layer.mask = shapeLayer;
    
    //path释放
    CGPathRelease(path);
}

- (void)_refreshDisplay {
    
    [self setNeedsDisplay];
}

- (void)clear {
    
    [self shouldDrawing:NO];
    
    for (NSUInteger i = 1; i < _numOfBins; i++) {
        
        dataBuffer[i] = 0;
        heightsByFrequency[i] = 0;
    }
    
    [self setNeedsDisplay];
}

#pragma mark - Free
- (void)freeBuffersIfNeeded {
    
    if (heightsByFrequency) {
        free(heightsByFrequency);
    }
    if (speeds) {
        free(speeds);
    }
    if (times) {
        free(times);
    }
    if (tSqrts) {
        free(tSqrts);
    }
    if (vts) {
        free(vts);
    }
    if (deltaHeights) {
        free(deltaHeights);
    }
    if (sinAngle) {
        free(sinAngle);
    }
}

#pragma mark - end

@end
